/*
 * Antonio Canabrava Fraideinberze 7152292
 * Lucas Avelino Sodre Santos      7239086
 * Pedro Rael Garcia               7153021
 */

/*
 * In this algorithm, each process finds a word that is a palindrome
 * and then sends it to root
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include <mpi.h>
#include <omp.h>

#include "sieve.h"
#include "avl.h"

#define MAX 1000
#define ROOT 0

int palindrome(char *word, int len);

int
main(int argc, char *argv[])
{
    FILE *ifp; /* Input file pointer*/
    FILE *ofp; /* Output file pointer*/
    char *buf; /* buffer where the file will be loaded */
    char word[MAX]; /* string that represents each word */
    char sieve[LAST_PRIME + 1]; /* string that represents the sieve*/
    int i; /* count variable */
    int j; /* aux variable */
    int k; /* aux variable */
    int res; /* aux variable */
    int sum; /* sum of each character */
    int low_value; /* low_value of each process */
    int high_value; /* high_value of each process */
    int id; /* id of process */
    int p; /* number of process */
    int tag = 0; /* tag for MPI_Recv e MPI_Send */
    int source; /* id source of the word */
    long f_size; /* size of the file */
    MPI_Status status; /* status for MPI_Recv*/
    /* Initialization of the MPI functions */
    MPI_Init(&argc, &argv);
    /* Important info for the program */
    MPI_Comm_rank(MPI_COMM_WORLD, &id);
    MPI_Comm_size(MPI_COMM_WORLD, &p);
    node *t = NULL; /* Initial node */

    /* Verifies if there is a file passed by argument */
    if(argc != 2)
    {
        printf("Usage: $big [file]");
        exit(1);
    }

    printf("Carregando arquivo...\n");
    /* Tries to open the input file in read-only mode */
    ifp = fopen(argv[1], "r");

    if(ifp == NULL)
    {
        fprintf(stderr, "Could not open the file. Exiting.\n");
        exit(1);
    }

    printf("Calculando o Crivo de Eratosthenes...\n");
    if(id == ROOT)
    {
        /* Tries to open the output file in write mode */
        ofp = fopen("palindromes", "w");

        if(ofp == NULL)
        {
            fprintf(stderr, "Could not create the file. Exiting.\n");
            exit(1);
        }

        /* Calculates the sieve */
        calc_sieve(sieve);
        /* Calculates the size of the file */
        fseek(ifp, 0, SEEK_END);
        f_size = ftell(ifp);
        rewind(ifp);

        /* Sends to every process the size of the file */
        for(i = 1; i < p; i++)
            MPI_Send(&f_size, 1, MPI_INT, i, tag, MPI_COMM_WORLD);
    }

    else
        MPI_Recv(&f_size, 1, MPI_INT, ROOT, tag, MPI_COMM_WORLD, &status);

    buf = (char *) malloc(f_size * sizeof(char));

    if(buf == NULL)
    {
        fprintf(stderr, "Could not allocate memory. Exiting.\n");
        exit(1);
    }

    res = fread(buf, 1, f_size, ifp);

    if(res != f_size)
    {
        fprintf(stderr, "Could not load the file. Exiting.\n");
        exit(1);
    }

    if(id != ROOT)
    {
        /* Calculates the lower, the higher value and the size of the block */
        low_value = ((id - 1) * f_size / (p - 1));
        high_value = 1 + ((id) * f_size / (p - 1));

        /* Offsets each pointer to get the whole word */
        while(!isalnum(buf[high_value]))
            high_value++;

        if(id != 1)
            while(!isalnum(buf[low_value]))
                low_value++;

        #pragma omp parallel for \
        shared(buf) firstprivate(low_value, high_value, word, i)

        for(res = low_value; res <= high_value; res++)
        {
            while(!isalnum(buf[res])) res++;

            i = 0;
            sum = 0;

            while(isalnum(buf[res]))
            {
                word[i] = tolower(buf[res]);
                res++;
                i++;
            }

            word[i] = '\0';
            /* If a word is palindrome and has more than 1 letter
             * sends it to root
             */
            #pragma omp critical

            if((i > 1) && 1 == palindrome(word, i))
            {
                MPI_Send(&i, 1, MPI_INT, ROOT, tag, MPI_COMM_WORLD);
                MPI_Send(&word, i, MPI_CHAR, ROOT, tag, MPI_COMM_WORLD);
            }
        }

        /* if a process has terminated, sends -1 to root */
        i = -1;
        MPI_Send(&i, 1, MPI_INT, ROOT, tag, MPI_COMM_WORLD);
    }

    else
    {
        printf("Montando a árvore AVL com os palíndromos...\n");
        k = p - 1;

        /* while each process has not terminated keeps receiving words */
        while(k > 0)
        {
            /* receives from any source a word length */
            MPI_Recv(&i, 1, MPI_INT, MPI_ANY_SOURCE, tag,
                     MPI_COMM_WORLD, &status);
            source = status.MPI_SOURCE;

            /* if this length is not terminated flag */
            if(i > 0)
            {
                /* receives from the source a whole word */
                MPI_Recv(&word, i, MPI_CHAR, source, tag,
                         MPI_COMM_WORLD, &status);
                word[i] = '\0';
                /* calculates if the word is prime */
                sum = 0;

                for(j = 0; j < i; j++)
                    sum += word[j];

                /* search it in the avl tree */
                if(find(word, t) == NULL)
                {
                    fprintf(ofp, "Word: %s - Len: %d - Sum: %d - Prime: %c\n",
                            word, i, sum, sieve[sum]);
                    t = insert(word, t);
                }
            }

            /* if the termination flag has sent */
            else k--;
        }
    }

    /* frees the buffers and file pointers */
    printf("Limpando os buffers...\n");
    free(buf);
    fclose(ifp);

    if(id == ROOT)
    {
        fclose(ofp);
        clear(t);
    }

    MPI_Finalize();
    return 0;
}

int
palindrome(char *word, int len)
{
    int i;

    for(i = 0; i < len; i++)
    {
        if(word[i] == word[len - i - 1])
            continue;

        else
            return 0;
    }

    return 1;
}

